12 Programmazione in rete
In Java la programmazione in rete è notevolmente semplificata e astratta molto bene in un insieme di classi.
Al fine di comunicare con un altro nodo della rete, è necessario connettersi con l’elaboratore giusto. Occorre dunque essere in grado di identificarlo univocamente mediante l’IP.
Esistono due modi per identificare l’IP:
Mediante DNS (Domain Name System) (Eg. appice.di.uniba.it)
Mediante dot notation (Eg. 194.207.187.85)
In Java si usa una speciale classe per rappresentare l’IP in entrambe le forme: InetAddress del package java.net
InetAddress addr = InetAddress.getByName(null); // restituisce l'indirizzo IP del "localhost"
InetAddress addr = InetAddress.getByName("127.0.0.1");
Un indirizzo IP non è sufficiente per individuare un server unico. Infatti possono esistere più server su una stessa macchina. Quando si imposta un client o un server è necessario scegliere la porta sul quale sia il server che il client decidono di connettersi.
Il programma client non deve conoscere soltanto l’indirizzo IP, ma anche la porta giusta per il servizio richiesto.
13 Socket
In Java si usa un socket per creare la connessione ad un’altra macchina, occorrerà disporre di un socket su ogni macchina.
Il socket è una astrazione software usata per rappresentare i terminali di una connessione tra due macchine. Creando un socket in Java, si ottengono un InputStream e un OutputStream al fine di abilitare la connessione in modo simile a un I/O su stream di oggetti.
Ci sono due classi socket basate su stream:
ServerSocket, che il server usa per ascoltare una richiesta di connessione.
Socket, usata dal client per inizializzare la connessione.
Una volta che un client richiede una connessione socket, il ServerSocket restituisce, mediante il metodo accept( ), un Socket attraverso il quale la comunicazione può avvenire dal lato server. Solo dopo che è avvenuto tutto ciò si ha una connessione Socket-to-Socket.
Quando si crea un ServerSocket, si specifica solo un numero di porta. Non occorre specificare un indirizzo IP poiché esso è già associato alla macchina sul quale il server gira. Al contrario, quando si crea un Socket lato client, occorre specificare tanto l’indirizzo IP quanto il numero di porta al quale connettersi. Il socket restituito da ServerSocket.accept( ) conterrà poi entrambe le informazioni.
A questo punto si usano i metodi getInputStream() e getOutputStream() per produrre i corrispondenti oggetti delle classi InputStream e OutputStream a partire dai singoli Socket.
13.1 Esempio di Client
import java.net.*;
import java.io.*;
public class Client {
public static void main(String[] args) {
InetAddress addr = InetAddress.getByName("127.0.0.1");
Socket socket = new Socket(addr, 8080);
// Pongo tutto in un blocco try-finally per assicurarmi che
// il socket sia chiuso:
try {
System.out.println("socket = " + socket);
BufferedReader in
= new BufferedReader(
new InputStreamReader(
.getInputStream()));
socket
// Flush automatico con PrintWriter col true:
PrintWriter out
= new PrintWriter(
new BufferedWriter(
new OutputStreamWriter(
.getOutputStream())), true);
socket
for (int i = 0; i < 10; i++) {
.println("prova " + i);
outString str = in.readLine();
// il client si ferma qui, in attesa di risposta dal server
System.out.println(str);
// stampa str dopo aver ricevuto risposta dal server
}
.println("END");
out} catch (IOException e) {
.printStackTrace();
e} finally {
System.out.println("closing...");
.close();
socket}
}
}
13.2 Esempio di server multi-thread
```java import java.io.;
import java.net.;
public class MultiJabberServer { static final int PORT = 8080; public static void main (String[] args) throws IOException { ServerSocket s = new ServerSocket(PORT); System.out.println(“Server Started”);
try {
while(true) {
// Si blocca finchè non si verifica una connessione:
Socket socket = s.accept();
// Connessione accettata
try {
new ServeOneJabber(socket);
} catch (IOException e) {
// Se fallisce chiude il socket
// altrimenti il thread la chiuderà:
socket.close();
}
}
} finally {
s.close();
// devo ricordarmi di chiudere sempre il server al termine
// della sua esecuzione altrimenti la porta rimarrà occupata
}
}
}
class ServeOneJabber extends Thread {
private Socket socket;
private BufferedReader in;
private PrintWriter out;
public ServeOneJabber(Socket s) throws IOException {
socket = s;
in = new BufferedReader(
new InputStreamReader(socket.getInputStream()));
out = new PrintWriter(
new BufferedWriter(
new OutputStreamWriter(socket.getOutputStream())), true);
// se una qualsiasi delle chiamate precedenti solleva una
// eccezione, il processo chiamante è responsabile della
// chiusura del socket. Altrimenti lo chiuderà il thread
start(); // Chiama run()
}
public void run() {
try {
while (true) {
// il server legge la riga dal client
String str = in.readLine();
if (str.equals("END")) {
break;
}
System.out.println("Echoing: " + str);
// il server rimanda str al client
out.println(str);
}
System.out.println("closing...");
} catch (IOException e) {
System.err.println("IO Exception");
} finally {
try {
socket.close();
} catch(IOException e) {
System.err.println("Socket not closed");
}
}
}
}